package com.free.bsf.eureka.client.customip;

import com.fasterxml.jackson.core.type.TypeReference;
import com.free.bsf.core.base.BsfException;
import com.free.bsf.core.http.HttpClient;
import com.free.bsf.core.util.HttpClientUtils;
import com.free.bsf.core.util.PropertyUtils;
import com.free.bsf.core.util.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.val;
import lombok.var;
import org.apache.http.entity.ContentType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

/**
 * @Classname FeignClientCustomIpAspects
 * @Description TODO
 * @Date 2021/4/28 10:03
 * @Created by chejiangyi
 */
@Aspect
public class FeignClientCustomIpAspect {
    @Pointcut("@within(org.springframework.cloud.openfeign.FeignClient)")
    public void pointCut(){};

    @Around("pointCut()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
        val method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        val feignClient= method.getDeclaringClass().getAnnotation(FeignClient.class);
        String name = feignClient.name();
        val host = PropertyUtils.getPropertyCache("bsf.feign.custom."+name+".host","");
        if(StringUtils.isEmpty(host))
            return joinPoint.proceed();
        String path = feignClient.path();
        Object params;
        if(joinPoint.getArgs().length==1){
            params=joinPoint.getArgs()[0];
        }else{
            Map<String,Object> ps = new HashMap<>();
            for(int i=0;i<method.getParameters().length;i++){
                val p = method.getParameters()[i];
                val arg= joinPoint.getArgs()[i];
                ps.put(p.getName(),arg);
            }
            params=ps;
        }
        val returnType = new TypeReference<Object>() {
                    @Override
                    public Type getType() {
                        return method.getReturnType();
                    }
                };
        val httpInfo = getMethod(method);
        if(httpInfo!=null) {
            val url = new URI(org.apache.commons.lang.StringUtils.stripEnd(host, "/")+
                    ( "/" + org.apache.commons.lang.StringUtils.strip(path, "/") + "/" + org.apache.commons.lang.StringUtils.stripStart(httpInfo.path, "/"))
                            .replace("//","/"));
            var request=HttpClient.Params.custom().setContentType(httpInfo.contentType).add(params).build();
            if ("post".equals(httpInfo.method)) {
                return HttpClientUtils.system().post(url.toASCIIString(),request , returnType);
            } else if ("get".equals(httpInfo.method)) {
                return HttpClientUtils.system().get(url.toASCIIString(), request, returnType);
            }
        }
        throw new BsfException("feign 自定义host仅支持post,get协议");
    }

    private HttpInfo getMethod(Method method){
        String httpMethod="";String httpPath="";
        var contentType=ContentType.APPLICATION_JSON;
        val reqMapping = method.getDeclaredAnnotation(RequestMapping.class);
        if(reqMapping!=null){
            httpMethod=reqMapping.method().length>0?reqMapping.method()[0].name():"";
            httpPath= reqMapping.value().length>0?reqMapping.value()[0]:"";
            contentType=reqMapping.consumes().length>0?ContentType.create(reqMapping.consumes()[0]):contentType;
            return new HttpInfo(httpMethod,httpPath,contentType);
        }
        val postMapping = method.getDeclaredAnnotation(PostMapping.class);
        if(postMapping!=null){
            httpMethod="post";
            httpPath= postMapping.value().length>0?postMapping.value()[0]:"";
            contentType=postMapping.consumes().length>0?ContentType.create(postMapping.consumes()[0]):contentType;
            return new HttpInfo(httpMethod,httpPath,contentType);
        }
        val getMapping = method.getDeclaredAnnotation(GetMapping.class);
        if(getMapping!=null){
            httpMethod="get";
            httpPath= getMapping.value().length>0?getMapping.value()[0]:"";
            contentType=getMapping.consumes().length>0?ContentType.create(getMapping.consumes()[0]):contentType;
            return new HttpInfo(httpMethod,httpPath,contentType);
        }
        return null;
    }

    @AllArgsConstructor
    @Data
    @Accessors
    private static class HttpInfo{
        String method;
        String path;
        ContentType contentType;
    }
}
